
// Copyright (C) 2011 Luca Piccioni
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//   
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

using Derm.Render;
using Derm.Scene.Mesh;
using Derm.Xml;

namespace Derm.Scene
{
	/// <summary>
	/// Complete scene used for rendering.
	/// </summary>
	public class RenderScene : IXmlSerializableExt
	{
		#region Constructors

		/// <summary>
		/// Parameterless constructor.
		/// </summary>
		public RenderScene()
		{
			
		}

		#endregion

		#region Scene Rendering

		public void EnsureCamera()
		{
			IList<RenderGraphNode> cameraNodes = mHierarchy.FindNodes(RenderGraphNodeType.CameraNode);

			if (cameraNodes.Count == 0) {
				RenderCameraGraphNode cameraGraphNode = new RenderCameraGraphNode("defaultCamera");
				Camera defaultSceneCamera = new Camera();

				Camera.PerspectiveParams defaultCameraProj = new Camera.PerspectiveParams();
				defaultCameraProj.VFov = 37.0f;
				defaultSceneCamera.SetProjectionParams(defaultCameraProj);
				
				cameraGraphNode.AssociatedCamera = defaultSceneCamera;

				cameraGraphNode.LocalModel.Translate(-427.749f, 333.855f, 655.017f);
				cameraGraphNode.LocalModel.Rotate(new Quaternion(Vertex3f.UnitY, -33.0f));
				cameraGraphNode.LocalModel.Rotate(new Quaternion(Vertex3f.UnitX, -22.0f));

				mHierarchy.RootNode.Link(cameraGraphNode);
			}
		}

		/// <summary>
		/// Select the first camera available for this scene.
		/// </summary>
		public void SelectFirstCamera()
		{
			IList<RenderGraphNode> cameraNodes = mHierarchy.FindNodes(RenderGraphNodeType.CameraNode);

			if (cameraNodes.Count == 0)
				throw new InvalidOperationException("no camera defined");

			mSceneView = (RenderCameraGraphNode) cameraNodes[0];
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="cameraId"></param>
		public void SelectCamera(string cameraId)
		{
			
		}

		/// <summary>
		/// Create resources collected in this scene.
		/// </summary>
		/// <param name="ctx"></param>
		public void Create(RenderContext ctx)
		{
			RenderHierarchy.TraversalContext graphCtx = new DirectedGraph<RenderGraphNode>.TraversalContext();

			RenderObjectGraphNode gridNode = new RenderObjectGraphNode("Grid");
			gridNode.AssociatedObject = new SceneGrid(1000.0f, 1000.0f);
			gridNode.LocalModel.SetRotateX(+90.0f);
			mHierarchy.RootNode.Link(gridNode);

			RenderObjectGraphNode gizmoNode = new RenderObjectGraphNode("SceneGizmo");
			gizmoNode.AssociatedObject = new MeshGizmo(200.0f);
			mHierarchy.RootNode.Link(gizmoNode);

			// Create all resources
			graphCtx.VisitorDelegate = delegate(RenderGraphNode graphNode, object data)
       		{
				if (graphNode.NodeType == RenderGraphNodeType.ObjectNode) {
					RenderObjectGraphNode objectGraphNode = (RenderObjectGraphNode) graphNode;

					objectGraphNode.AssociatedObject.Create(ctx);
				}
       		};
			mHierarchy.TraverseParentFirst(graphCtx, null);

			// Depth test is enabled
			mHierarchy.RootNode.State.DefineState(new DepthTestState(DepthTestState.DepthFunction.LessEqual));

			// Test
			//mHierarchy.RootNode.State.DefineState(new PolygonModeState(PolygonModeState.Face.FrontAndBack, PolygonModeState.Mode.Line));
			mHierarchy.RootNode.State.DefineState(new CullFaceState(CullFaceState.FrontFace.CounterClockWise));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="ctx"></param>
		public void Render(RenderContext ctx)
		{
			// Point of view projection
			mSceneView.AssociatedCamera.SetupProjection(ctx);

			// Transform world following camera
			mHierarchy.RootNode.LocalModel.Set(mSceneView.WorldModel.GetInverseMatrix());

			// Render scene
			mHierarchy.Render(ctx);
		}

		/// <summary>
		/// Hierarchy determining the current scene configuration.
		/// </summary>
		public RenderHierarchy Hierarchy { get { return (mHierarchy); } set { mHierarchy = value; } }

		/// <summary>
		/// Hierarchy determining the current scene configuration.
		/// </summary>
		private RenderHierarchy mHierarchy;

		/// <summary>
		/// Current view used for presenting scene.
		/// </summary>
		private RenderCameraGraphNode mSceneView;

		#endregion

		#region Scene Loading & Saving

		/// <summary>
		/// Load a RenderScene from a file.
		/// </summary>
		/// <param name="path"></param>
		/// <returns></returns>
		public static RenderScene Load(string path)
		{
			if (path == null)
				throw new ArgumentNullException("path");

			// Load XML document (validated)
			using (StreamReader sr = new StreamReader(path)) {
				return ((RenderScene) XmlHelper.Deserialize(sr.BaseStream, typeof(RenderScene), XmlHelper.DermNamespace));
			}
		}

		/// <summary>
		/// XmlSerializer used for serializing this class.
		/// </summary>
		private static readonly XmlSerializer sRenderSceneSerializer = new XmlSerializer(typeof(RenderScene));

		#endregion

		#region IXmlSerializableExt Implementation

		/// <summary>
		/// This method is reserved (instead use System.Xml.Serialization.XmlSchemaProviderAttribute to the class instead).
		/// </summary>
		/// <returns>
		/// It always returns null.
		/// </returns>
		public XmlSchema GetSchema() { return (null); }

		/// <summary>
		/// Generates an object from its XML representation.
		/// </summary>
		/// <param name="xmlReader">
		/// The System.Xml.XmlReader stream from which the object is deserialized.
		/// </param>
		public void ReadXml(XmlReader xmlReader)
		{
			XmlHelper.ReadXml(xmlReader, this);
		}

		/// <summary>
		/// Converts an object into its XML representation.
		/// </summary>
		/// <param name="writer">
		/// The System.Xml.XmlWriter stream to which the object is serialized.
		/// </param>
		public void WriteXml(XmlWriter writer)
		{
			// Element: current view
			if (mSceneView != null) {
				writer.WriteComment("Initially selected view");
				writer.WriteStartElement(XmlNameCurrentView);
				writer.WriteAttributeString(XmlNameCurrentViewIdentifier, mSceneView.Identifier);
			}

			// Element: scene hierarchy
			if (mHierarchy != null) {
				writer.WriteComment("Scene hierarchy");
				writer.WriteStartElement(XmlNameRenderHierarchy);
				mHierarchy.WriteXml(writer);
				writer.WriteEndElement();
			}
		}

		/// <summary>
		/// Read the attribute of the current XML serializable.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading element attributes.
		/// </param>
		public void ReadAttributes(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");
		}

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element.
		/// </param>
		public void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			if        (elementName == XmlNameRenderHierarchy) {
				// Allocate child element
				mHierarchy = new RenderHierarchy();
				// Read child element
				mHierarchy.ReadXml(xmlReader);
			} else if (elementName == XmlNameResources) {
				RenderResourceDb resourceDb = new RenderResourceDb();

				// Load scene graph resources
				XmlHelper.ReadXml(xmlReader, resourceDb);
				// Merge resource with the global database
				RenderResourceDb.GlobalDb.Merge(resourceDb);
			} else
				throw new XmlException(String.Format("unexpected element {0}", elementName));
		}

		/// <summary>
		/// Read the text of the current XML serializable.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		public void ReadText(XmlReader xmlReader)
		{
			
		}

		/// <summary>
		/// Name of the 'sXmlNameCurrentView' attribute that specify the currently selected view identifier.
		/// </summary>
		private const string XmlNameCurrentViewIdentifier = "Identifier";

		/// <summary>
		/// Name of the element that specify the currently selected view.
		/// </summary>
		private const string XmlNameCurrentView = "CurrentView";

		/// <summary>
		/// Name of the element that specify the graph hierarchy.
		/// </summary>
		private const string XmlNameRenderHierarchy = "ObjectHierarchy";

		/// <summary>
		/// Name of the element that specify the graph hierarchy.
		/// </summary>
		private const string XmlNameResources = "Resources";

		#endregion
	}
}
